Arquivos:
* bank. csv: uma versão reduzida do conjunto de dados;
* bank-full.csv: o conjunto completo;
* bank-names.txt: com a descrição dos campos do conjunto de dados.
Para a resolução deste teste utilize o arquivo bank-full.csv e para uma descrição sobre o dataset use bank-names.txt e/ou a fonte oficial
O objetivo da classificação é prever se o cliente assinará um depósito a prazo (variável y).
Importando as bibliotecas iniciais para análise e visualização de dados
#Data Analisys
import pandas as pd
import numpy as np
#Data Viz
import matplotlib.pyplot as plt
import seaborn as sns
from plotly import __version__
from plotly.offline import download_plotlyjs, init_notebook_mode, plot, iplot
init_notebook_mode(connected=True)
%matplotlib inline
1 - age: idade do cliente
2 - job : tipo do trabalho
3 - marital : estado civil
4 - education: Formação
5 - default: tem crédito por padrão?
6 - balance: saldo médio anual, em Euros
7 - housing: tem empréstimo habitacional?
8 - loan: tem empréstimo pessoal?
9 - contact: tipo de comunicação de contato
10 - day: último dia do mês do contato
11 - month: mês do último contato do ano
12 - duration: duração do último contato, em segundos
13 - campaign: número de contatos realizados durante esta campanha e para este cliente
14 - pdays: número de dias que passaram após o último contato do cliente de uma campanha anterior (-1 significa que o cliente não foi contatado anteriormente)
15 - previous: número de contatos realizados antes desta campanha e para este cliente
16 - poutcome: resultado da campanha de marketing anterior
17 - y - o cliente assinou um depósito a prazo?
Lendo os datasets citados acima
bank_full = pd.read_csv('bank-full.csv', sep=';')
bank = pd.read_csv('bank.csv', sep =';')
print("Base: bank-full.csv\n")
print("Linhas = ", bank_full.shape[0])
print("Colunas = ", bank_full.shape[1])
print("\n\nBase: bank.csv\n")
print("Linhas = ", bank.shape[0])
print("Colunas = ", bank.shape[1])
Com o método describe, podemos obter algumas métricas estatisticas sobre nossas colunas númericas
bank.head()
bank.describe()
Obtendo iformações sobre as colunas do dataframe
Nome | Quantidade de registros | Tipo de dado da coluna
bank.info()
Uma alternativa ao método describe, é a utilização do relatório automatizado da biblioteca pandas profiling, no qual fornece mais informações sobre o dataset, como métricas estatísticas, valores faltantes,warnings, histogramas para valores numéricos, counts para variáveis qualitativas, etc
import pandas_profiling
bank.profile_report(style={'full_width':True})
Respondendo as perguntas citadas no início
Realizando a contagem de profissões que realizaram empréstimo
job_yes = bank_full.loc[bank_full['y']=='yes']['job'].value_counts()
job_yes
Realizando a contagem de profissões que não realizaram empréstimo
job_no = bank_full.loc[bank_full['y']=='no']['job'].value_counts()
job_no
Realizando a contagem de todas as profissões
total_jobs = bank_full['job'].value_counts()
total_jobs
Criando um DataFrame para organizar todas as informções
jobs = pd.DataFrame(data = {'Job': total_jobs, 'Yes': job_yes, 'No': job_no})
jobs.rename(columns={'Job':'Total'}, inplace=True)
jobs = jobs.sort_values(by='Yes', ascending = False)
Criando novos campos:
Total = Total de respostas baseado na profissão em questão
% Yes = Porcentagem de emprestimos realizados baseado na profissão em questão
% No = Porcentagem de emprestimos não realizados baseado na profissão em questão
% Total_Yes = Porcentagem de emprestimos realizados baseado na profissão em questão em relação com todos os registros
% Total_No = Porcentagem de emprestimos não realizados baseado na profissão em questão em relação com todos os registros
jobs['% Yes'] = jobs['Yes'] / jobs['Total'] * 100
jobs['% No'] = jobs['No'] / jobs['Total'] * 100
jobs['% Total_Yes'] = jobs['Yes'] / jobs['Total'].sum() *100
jobs['% Total_No'] = jobs['No'] / jobs['Total'].sum() *100
jobs
Criando gráficos para melhor visualização dos dados
from plotly import graph_objs as go
fig = go.Figure()
fig.add_trace(go.Bar(
y=jobs.index,
x=jobs['% Yes'],
name='Yes',
orientation='h',
marker=dict(
color='rgba(0, 255, 93, 0.6)',
line=dict(color='rgba(0, 255, 93, 1.0)', width=3)
)
))
fig.add_trace(go.Bar(
y=jobs.index,
x=jobs['% No'],
name='No',
orientation='h',
marker=dict(
color='rgba(227, 23, 32, 0.6)',
line=dict(color='rgba(227, 23, 32, 1.0)', width=3)
)
))
fig.layout.update(barmode ='stack', title_text="% de Aprovação / Reprovação de Emprestimos em cada Profissão")
fig.show()
labels = jobs.index
values = jobs['% Total_Yes']
fig = go.Figure(data=[go.Pie(labels=labels, values=values)])
fig.layout.update(title_text="% Emprestimos aprovados por profissão")
fig.show()
Baseado nos gráficos acima, podemos observar que as seguintes profissões que realizam mais emprestimos são:
TOP 5 :
1. management
2. technician
3. blue-collar
4. admin
5. retired
Outra curiosidade é que, analisando por profissão, quais aceitam mais propostas de emprestimo são:
TOP 3:
1. student
2. retired
3. unemployed
yes_camp = bank_full.loc[bank_full['y']=='yes']['campaign']
no_camp = bank_full.loc[bank_full['y']=='no']['campaign']
fig = go.Figure()
fig.add_trace(go.Histogram(x=yes_camp, name = 'Yes'))
fig.add_trace(go.Histogram(x= no_camp, name = 'No'))
fig.layout.update(barmode='stack', title_text="Histograma de Ligações segmentado pelo status do emprestimo")
fig.show()
Conforme notamos no histograma acima, há alguns outliers nos nossos dados
Vamos plotar um Box Plot para identificar onde estão os outliers
fig = go.Figure()
fig.add_trace(go.Box(y=yes_camp, name='Yes'))
fig.add_trace(go.Box(y=no_camp, name = 'No'))
fig.layout.update(title_text="Boxplot de Ligações segmentado pelo status do emprestimo")
fig.show()
print('1/4: ', bank_full['campaign'].quantile(q = 0.25))
print('2/4: ', bank_full['campaign'].quantile(q = 0.50))
print('3/4: ', bank_full['campaign'].quantile(q = 0.75))
print('4/4: ', bank_full['campaign'].quantile(q = 1.00))
print('\n\nQuantidade de ligações acima de', bank_full['campaign'].quantile(q = 0.75) +
1.5*(bank_full['campaign'].quantile(q = 0.75) - bank_full['campaign'].quantile(q = 0.25)), 'são outliers.')
Realizando novamente a plotagem do histograma, porém sem os outliers
yes_camp = bank_full.loc[bank_full['y']=='yes']['campaign']
no_camp = bank_full.loc[bank_full['y']=='no']['campaign']
fig = go.Figure()
fig.add_trace(go.Histogram(x=yes_camp.loc[yes_camp <=6], name = 'Yes'))
fig.add_trace(go.Histogram(x= no_camp.loc[no_camp <=6], name = 'No'))
fig.layout.update(barmode='stack', title_text="Histograma de Ligações segmentado pelo status do emprestimo")
fig.show()
A relação entre o número de contatos e o sucesso da campanha é que, na maioria dos casos, o cliente realiza o emprestimo nas primeiras ligações.
Insistir em uma grande quantidade de contatos pode gerar custos desnecessários e um baixo retorno (Emprestimos realizados).
print("Número médio é de {} ligações".format(round(yes_camp.loc[yes_camp <=6].mean())))
Criando um DataFrame para verificar a Quantidade de Ligações em comparação com a quantidade de emprestimos fechados
calls = pd.DataFrame(data ={'NumLigacoes':yes_camp.value_counts().index,
'QuantEmprestimos': yes_camp.value_counts().values})
Realizando o cálculo de Frequencia
calls['Fi'] = calls['QuantEmprestimos'] / calls['QuantEmprestimos'].sum()
calls['Fac'] = 1.0
calls = calls.sort_values(by = 'NumLigacoes', ascending = True).reset_index(drop= True)
Criando uma função para realizar o calculo da Frequência Acumulada (Fac)
def Fac(df):
for i in range(0,df.shape[0]-1):
if(i==0):
df['Fac'][i] = df['Fi'][i]
else:
df['Fac'][i] = df['Fi'][i] + df['Fac'][i-1]
Executando a função Fac( )
Fac(calls)
Verificando como ficou nosso DataFrame após a execução da função:
calls.head(30)
Observamos que agora temos a coluna de Frequência Acumulada, entretanto, os dados ficam mais visiveis em um gráfico.
Vamos plotar um LinePlot para analisar
import plotly.express as px
fig = px.line(calls.head(15), x="NumLigacoes", y="Fac", title="Freq Acumulada vs Quant. Ligações")
fig.show()
Podemos notar no gráfico acima, que até 5 contatos adesão continua crescendo, entretanto após este valor, a quantidade de adesão acaba ficando quase estagnada.
Logo, o recomendado seria de 1 a 5 ligações por cliente
camp = bank_full[['day', 'month', 'duration', 'campaign', 'pdays', 'previous', 'poutcome', 'y']]
camp.loc[camp['pdays']>0]
fig = px.histogram(camp.loc[camp['pdays']>0], x="poutcome", color="y", title="Influência da Campanha Anterior")
fig.show()
prev_camp = camp.loc[(camp['pdays']>0) & (camp['y']=='yes')]['poutcome'].value_counts() / camp.loc[camp['pdays']>0]['poutcome'].value_counts()
labels = prev_camp.index
values = prev_camp.values
fig = go.Figure(data=[go.Pie(labels=labels, values=values)])
fig.layout.update(title_text="% Emprestimos aprovados por Status da Campanha Anterior")
fig.show()
Com base nos gráficos acima, constatamos que o resultado da campanha anterior tem influência na contratação do emprestimo.
Aproximadamente 48 % das campanhas anteriores com status de sucesso resultaram na contratação do emprestimo
fig = px.histogram(bank_full, x="job", color="housing", title="Emprestimo Imobiliário por Emprego")
fig.show()
fig = px.histogram(bank_full, x="marital", color="housing", title="Emprestimo Imobiliário por Estado Civíl")
fig.show()
fig = px.histogram(bank_full, x="education", color="housing", title="Emprestimo Imobiliário por Formação")
fig.show()
fig = px.histogram(bank_full.loc[(bank_full['balance']<=10000) & (bank_full['balance']>0)],
x="balance", color="housing", title="Emprestimo Imobiliário por Salario Anul(EUR)")
fig.show()
fig = px.histogram(bank_full, x="loan", color="housing", title="Emprestimo Imobiliário e Emprestimo Pessoal")
fig.show()
Algumas caracteristicas predominantes de pessoas que possuem imprestimo imobiliário
Preparando os dados para aplicar os modelos de machine Learning
Importando a biblioteca de pré processamento para tratar as colunas com dados categóricos
from sklearn.preprocessing import LabelEncoder
Aplicando os métodos da classe LabelEnconder, podemos tratar de forma eficiente os dados categoricos, atribuindo um valor numéricos para cada valor categórico da coluna
Como possuímos diversar colunas com dados categóricos, usarei um laço para percorrer todas.
#Array com todas as colunas numéticas
categorical_column = ['job', 'marital', 'education', 'default', 'housing', 'loan', 'contact', 'month','poutcome']
#Laço percorrendo todas as colunas e aplicando a transformação
for i in categorical_column:
le = LabelEncoder()
bank_full[i] = le.fit_transform(bank_full[i])
#Exibe o dataframe após a transformação
bank_full.head()
Antes de realizar a divisão dos dados entre treino e teste, organizar as variáveis preditoras e a variável a ser predita pode facilitar o processo e o entendimento
y_map = {'yes':1, 'no':0}
# Target = Somente a coluna 'y'
target = bank_full['y'].map(y_map)
# features = Todas as colunas, execeto a variável Target
features = bank_full.drop('y', axis=1)
Utilizando o método train_test_split podemos segmentar nossos dados de treino e teste de forma eficiente
from sklearn.model_selection import train_test_split
Iremos utilizar a divisão "padrão", utilizando 70% dos dados para treino e 30% dos dados para teste
X_train, X_test, y_train, y_test = train_test_split(features, target, test_size=0.30, random_state=101)
Exibindo as informações sobre os conjuntos de dados de treino e teste
#Informações sobre o dataset de Treino
print("Treino = " ,round((X_train.shape[0]/bank_full.shape[0])*100),"%")
print("X = ",X_train.shape[0])
print("Y = ",len(y_train))
#Informações sobre o dataset de Teste
print("\nTeste = " ,round((X_test.shape[0]/bank_full.shape[0])*100),"%")
print("X = ",X_test.shape[0])
print("Y = ",len(y_test))
Com nosso dados tratados e separados, podemos dar inicio aos nossos modelos de Machine Leaning
Importando as bibliotecas essenciais para iniciar os modelos
from sklearn.model_selection import KFold
from sklearn.model_selection import cross_val_score
from sklearn.metrics import confusion_matrix, accuracy_score, classification_report
k_fold = KFold(n_splits=10, shuffle=True, random_state=0)
Importando o classificador KNN
from sklearn.neighbors import KNeighborsClassifier
Implementando o modelo e realizando as predições
#Criando o objeto do KNN
knn = KNeighborsClassifier(n_neighbors=5)
#"Treinando" o modelo KNN
knn.fit(X_train, y_train)
#Realizando as predições
knnpred = knn.predict(X_test)
Avaliação do modelo KNN
print("Relatório de Classificação\n\n",classification_report(y_test, knnpred))
print("Acurácia =",round(accuracy_score(y_test, knnpred),2)*100,"\n")
print("Matriz de Confusão\n\n",confusion_matrix(y_test, knnpred))
KNNCV = (cross_val_score(knn, X_train, y_train, cv=k_fold, n_jobs=1, scoring = 'accuracy').mean())
Podemos utilizar um loop no valor de K para avaliar onde o modelo terá uma erro menor
Obs.: Essa etapa pode consumir um pouco de tempo e processamento
error_rate = []
for i in range(1,20):
knn = KNeighborsClassifier(n_neighbors=i)
knn.fit(X_train,y_train)
pred_i = knn.predict(X_test)
error_rate.append(np.mean(pred_i != y_test))
error_rate
plt.figure(figsize=(10,6))
plt.plot(range(1,20),error_rate,color='blue', linestyle='dashed', marker='o',
markerfacecolor='red', markersize=10)
plt.title('Error Rate vs. K Value')
plt.xlabel('K')
plt.ylabel('Error Rate')
Conforme vemos acima, o gráfico nos mostra que a menor taixa de erro é para K= 20
Vamos realizar novamente a predição, porém com um novo valor em K
knn = KNeighborsClassifier(n_neighbors=7)
knn.fit(X_train,y_train)
pred = knn.predict(X_test)
Nova avaliação do modelo
print("Acurácia =",round(accuracy_score(y_test, knnpred),2)*100,"\n")
print("Matriz de Confusão\n\n",confusion_matrix(y_test, knnpred))
KNNCV = (cross_val_score(knn, X_train, y_train, cv=k_fold, n_jobs=1, scoring = 'accuracy').mean())
Importando o classificador do Gradient Boosting
from sklearn.ensemble import GradientBoostingClassifier
Implementando o modelo e realizando as predições
#Criando o objeto da classe
gbk = GradientBoostingClassifier()
#Treinamento do modelo
gbk.fit(X_train, y_train)
#Realizando as predições
gbkpred = gbk.predict(X_test)
Avaliação do modelo
print("Relatório de Classificação", classification_report(y_test, gbkpred))
print("Matriz de Confusão\n\n",confusion_matrix(y_test, gbkpred ))
GBKCV = (cross_val_score(gbk, X_train, y_train, cv=k_fold, n_jobs=1, scoring = 'accuracy').mean())
print("Acurácia =",round(accuracy_score(y_test, gbkpred),2)*100)
Importando o classificador do Support Vector Machine
from sklearn.svm import SVC
Repetindo os processos de transformação, porem para os dados menores, uma vez que o SVM tem alto consumo de recursos computacionais
#Laço percorrendo todas as colunas e aplicando a transformação
for i in categorical_column:
le = LabelEncoder()
bank[i] = le.fit_transform(bank[i])
y_map = {'yes':1, 'no':0}
# Target = Somente a coluna 'y'
target_small = bank['y'].map(y_map)
# features = Todas as colunas, execeto a variável Target
features_small = bank.drop('y', axis=1)
X_train_small, X_test_small, y_train_small, y_test_small = train_test_split(features_small, target_small, test_size=0.30, random_state=101)
#Criando o objeto do SVM com os valores padrões
svc= SVC(gamma ='auto')
#Treinamento do modelo
svc.fit(X_train_small, y_train_small)
#Realizando as predições
svcpred = svc.predict(X_test_small)
Avaliação do modelo
print("Relatório de Classificação", classification_report(y_test_small, svcpred))
print("Matriz de Confusão",confusion_matrix(y_test_small, svcpred))
print("Acurária =",round(accuracy_score(y_test_small, svcpred),2)*100)
SVCCV = (cross_val_score(svc, X_train_small, y_train_small, cv=k_fold, n_jobs=1, scoring = 'accuracy').mean())
Podemos utilizar o Gridsearch, função que permite testar diversas combinações de parâmetros nos nossos modelos, facilitando como achar os melhores parametros para otimizar o modelo.
Realizamos uma operação semelhante acima no modelo KNN, para encontrar o melhor valor de K, entretanto, isso pode ser um pouco menos eficiente que a utilização do GridSearch
Importando a classe GridSearchCV
from sklearn.model_selection import GridSearchCV
Definindo os parametros que serão variados no treino do modelo SVC
param_grid = {'C': [0.1,1, 10, 100, 1000], 'gamma': [1,0.1,0.01,0.001,0.0001], 'kernel': ['rbf']}
Implementando o modelo
#Criando o objeto GridSearch
grid = GridSearchCV(SVC(),param_grid,refit=True,verbose=3)
#Treinando o modelo
grid.fit(X_train_small,y_train_small)
Verificando os parametros gerados pelo GridSearch
print("Melhores parametros:", grid.best_params_)
print("Melhor pontuação:", grid.best_score_)
Realizando as predições
grid_predictions = grid.predict(X_test_small)
Avaliando o modelo
print("Matriz de Confusão\n", confusion_matrix(y_test_small,grid_predictions))
print("Relatório de Classificação\n\n",classification_report(y_test_small,grid_predictions))
models = pd.DataFrame({
'Modelos': ['SVM', 'KNN', 'GRADIENT BOOST'],
'Pontuação': [SVCCV, KNNCV, GBKCV]})
models.sort_values(by='Pontuação', ascending=False)
De acordo com os resultados, o modelo com maior eficiência seria o Gradient Boost